﻿using Microsoft.Extensions.Options;
using Sgr.DistributedLock;
using Sgr.Redis;
using Sgr.Utilities;
using StackExchange.Redis;
using System.Text.Json;

namespace Sgr.Caching.Services
{
    /// <summary>
    /// Redis缓存管理器实现，提供基于Redis的分布式缓存功能
    /// </summary>
    /// <remarks>
    /// 已实现的缓存穿透防护措施：
    /// 1. 对空值(null)进行缓存，使用较短的过期时间
    /// 2. 空值过期时间由CacheOptions.NullValueExpirationSeconds配置
    /// 3. 缓存项的反序列化结果即使为null也会返回，避免重复查询
    /// 4. 使用分布式锁防止缓存击穿，确保同一时间只有一个请求可以重建缓存
    /// </remarks>
    public class RedisCacheManager : ICacheManager
    {
        private const int DeleteKeysBatchSize = 1000;
        private const long NotPresent = -1;
        private const double SlidingRefreshSkipRatio = 0.6;// 当当前 TTL 大于 (目标窗口 * 比例) 时跳过刷新，减少写放大

        /// <summary>
        /// Redis缓存设置脚本
        /// KEYS[1] = 缓存的key
        /// ARGV[1] = 绝对过期时间（ticks，-1表示无过期）
        /// ARGV[2] = 滑动过期时间（ticks，-1表示无过期）
        /// ARGV[3] = 相对过期时间（秒，-1表示无过期）
        /// ARGV[4] = 缓存数据（序列化后的字符串）
        ///
        /// HSET是 Redis 的 Hash 结构操作命令，用于设置一个 Hash Key 中的字段（field）和值（value）
        /// 在 Redis 中以 Hash 结构存储一组数据（包括绝对过期时间、滑动过期时间、实际数据内容），并根据传入参数决定是否为这个 Hash Key 设置一个过期时间（EXPIRE）
        /// 存入了三个字段：
        ///     'absexp'：表示 absolute expiration（绝对过期时间，比如一个时间戳）
        ///     'sldexp'：表示 sliding expiration（滑动过期时间，比如一个持续时间）
        ///     'data'：  表示 实际要存储的业务数据（如 JSON、序列化对象等）
        /// </summary>
        private const string SetScript = (@"
            redis.call('HSET', KEYS[1], 'absexp', ARGV[1], 'sldexp', ARGV[2], 'data', ARGV[4])
            if ARGV[3] ~= '-1' then
              redis.call('EXPIRE', KEYS[1], ARGV[3])
            end
            return 1");

        private const string AbsoluteExpirationKey = "absexp";
        private const string SlidingExpirationKey = "sldexp";
        private const string DataKey = "data";

        // 优化：使用只读字段存储常用的哈希成员数组
        private static readonly RedisValue[] HashFieldsWithData = new RedisValue[]
        {
            AbsoluteExpirationKey,
            SlidingExpirationKey,
            DataKey
        };

        private static readonly RedisValue[] HashFieldsWithoutData = new RedisValue[]
        {
            AbsoluteExpirationKey,
            SlidingExpirationKey
        };

        /// <summary>
        /// 获取哈希字段数组
        /// </summary>
        private static RedisValue[] GetHashFields(bool withData) =>
            withData ? HashFieldsWithData : HashFieldsWithoutData;

        private readonly IRedisDatabaseContext _redisDatabaseContext;
        private readonly CacheOptions _cacheOptions;
        private readonly RedisDistributedLock _distributedLock;

        // 抽取锁键生成逻辑
        private static string GetLockKey(string cacheKey) => $"cache:lock:{cacheKey}";

        /// <summary>
        /// 初始化Redis缓存管理器
        /// </summary>
        /// <param name="options">缓存配置选项</param>
        /// <param name="redisDatabaseContext">Redis数据库上下文</param>
        /// <param name="distributedLock">分布式锁</param>
        public RedisCacheManager(
            IOptions<CacheOptions> options,
            IRedisDatabaseContext redisDatabaseContext,
            RedisDistributedLock distributedLock)
        {
            _cacheOptions = options?.Value ?? throw new ArgumentNullException(nameof(options));
            _redisDatabaseContext = redisDatabaseContext ?? throw new ArgumentNullException(nameof(redisDatabaseContext));
            _distributedLock = distributedLock ?? throw new ArgumentNullException(nameof(distributedLock));
        }

        #region Serializer

        /// <summary>
        /// 序列化对象为RedisValue
        /// </summary>
        private static RedisValue SerializeObject(object? obj)
        {
            //return JsonHelper.SerializeObject(obj);
            return JsonSerializer.Serialize(obj, JsonHelper.GetJsonSerializerOptions());
        }

        /// <summary>
        /// 反序列化RedisValue为指定类型对象
        /// </summary>
        private static T? DeserializeObject<T>(RedisValue value)
        {
            //return JsonSerializer.Deserialize<T>(value.ToString(), JsonHelper.GetJsonSerializerOptions());
            return JsonHelper.DeserializeObject<T>(value.ToString());
        }

        #endregion Serializer

        #region ICacheManager

        public bool IsLocalCache => false;

        public void Set(string key, object? obj, CacheEntryOptions? cacheEntryOptions = null)
        {
            Check.StringNotNullOrEmpty(key, nameof(key));

            var effectiveOptions = PrepareEffectiveOptions(obj, cacheEntryOptions);
            var creationTime = DateTime.UtcNow;

            var absExp = GetAbsoluteExpiration(creationTime, effectiveOptions);
            var ttlSeconds = GetExpirationInSeconds(creationTime, absExp, effectiveOptions);
            if (ttlSeconds is <= 0) return; // 仅阻止0/负，允许 null 继续写入（无TTL）

            var db = _redisDatabaseContext.Connect();
            try
            {
                db.ScriptEvaluate(
                    SetScript,
                    new RedisKey[] { _redisDatabaseContext.InstancePrefix.Append(key) },
                    new RedisValue[]
                    {
                        absExp?.Ticks ?? NotPresent,
                        effectiveOptions.SlidingExpiration?.Ticks ?? NotPresent,
                        ttlSeconds ?? NotPresent,
                        SerializeObject(obj)
                    });
            }
            catch (Exception ex)
            {
                _redisDatabaseContext.OnRedisError(ex, db);
                throw;
            }
        }

        public async Task SetAsync(string key, object? obj, CacheEntryOptions? cacheEntryOptions = null, CancellationToken token = default)
        {
            Check.StringNotNullOrEmpty(key, nameof(key));
            token.ThrowIfCancellationRequested();

            var effectiveOptions = PrepareEffectiveOptions(obj, cacheEntryOptions);
            var creationTime = DateTime.UtcNow;

            var absExp = GetAbsoluteExpiration(creationTime, effectiveOptions);
            var ttlSeconds = GetExpirationInSeconds(creationTime, absExp, effectiveOptions);
            if (ttlSeconds is <= 0) return; // 仅阻止0/负，允许 null 继续写入（无TTL）

            var db = await _redisDatabaseContext.ConnectAsync(token).ConfigureAwait(false);
            try
            {
                await db.ScriptEvaluateAsync(
                    SetScript,
                    new RedisKey[] { _redisDatabaseContext.InstancePrefix.Append(key) },
                    new RedisValue[]
                    {
                        absExp?.Ticks ?? NotPresent,
                        effectiveOptions.SlidingExpiration?.Ticks ?? NotPresent,
                        ttlSeconds ?? NotPresent,
                        SerializeObject(obj)
                    }).ConfigureAwait(false);
            }
            catch (Exception ex)
            {
                _redisDatabaseContext.OnRedisError(ex, db);
                throw;
            }
        }

        public (bool Success, TItem? Value) TryGetValue<TItem>(string key)
        {
            Check.StringNotNullOrEmpty(key, nameof(key));
            var db = _redisDatabaseContext.Connect();
            return ReadFromRedis<TItem>(db, key);
        }

        public async Task<(bool Success, TItem? Value)> TryGetValueAsync<TItem>(string key, CancellationToken token = default)
        {
            Check.StringNotNullOrEmpty(key, nameof(key));
            token.ThrowIfCancellationRequested();
            var db = await _redisDatabaseContext.ConnectAsync(token).ConfigureAwait(false);
            return await ReadFromRedisAsync<TItem>(db, key, token).ConfigureAwait(false);
        }

        /// <summary>
        /// 异步获取缓存项，如果不存在则通过指定的委托获取数据并设置缓存
        /// </summary>
        /// <typeparam name="TData">数据类型</typeparam>
        /// <param name="key">缓存键</param>
        /// <param name="acquire">获取数据的委托，可以返回null值</param>
        /// <param name="cacheEntryOptions">缓存项配置选项</param>
        /// <param name="token">取消令牌</param>
        /// <returns>表示异步操作的任务，包含获取的数据。数据可能为null</returns>
        /// <remarks>
        /// 该方法使用分布式锁机制防止缓存击穿：
        /// 1. 首先尝试从缓存中获取数据
        /// 2. 如果缓存不存在，则尝试获取分布式锁
        /// 3. 获取锁成功后，再次检查缓存（双重检查锁定模式）
        /// 4. 如果缓存仍不存在，则执行委托获取数据并更新缓存
        /// 5. 如果未能获取锁，则等待短暂时间后再次尝试从缓存获取
        /// 6. 如果仍未命中缓存，则执行委托获取数据，但不更新缓存，避免与持有锁的线程冲突
        /// </remarks>
        public async Task<TData?> GetAsync<TData>(string key, Func<Task<TData?>> acquire, CacheEntryOptions? cacheEntryOptions = null, CancellationToken token = default)
        {
            Check.StringNotNullOrEmpty(key, nameof(key));
            token.ThrowIfCancellationRequested();

            var db = await _redisDatabaseContext.ConnectAsync(token).ConfigureAwait(false);

            // 首次尝试从Redis读取缓存
            var (hit, value) = await ReadFromRedisAsync<TData>(db, key, token);
            if (hit) return value;

            // 获取分布式锁以防止缓存击穿
            var lockKey = GetLockKey(key);
            // 尝试获取分布式锁，最多等待300ms
            var locker = await _distributedLock.TryAcquireLockAsync(lockKey,
                TimeSpan.FromMilliseconds(300),
                TimeSpan.FromMilliseconds(_cacheOptions.LockTimeoutMilliseconds));

            try
            {
                if (locker != null)
                {
                    // 获得锁后再次检查缓存（双重检查锁定模式）
                    (hit, value) = await ReadFromRedisAsync<TData>(db, key, token);
                    if (hit) return value;

                    // 缓存确实不存在，执行获取数据的操作
                    var newData = await acquire();

                    // 对空值也进行缓存，设置较短的过期时间
                    var options = PrepareEffectiveOptions(newData, cacheEntryOptions);
                    await SetAsync(key, newData, options, token);
                    return newData;
                }
                else
                {
                    // 未获得锁，说明其他线程正在重建缓存
                    // 等待短暂时间后再次尝试从缓存获取
                    await Task.Delay(100, token);
                    (hit, value) = await ReadFromRedisAsync<TData>(db, key, token);
                    if (hit) return value;

                    // 如果仍然没有缓存，则执行获取数据的操作（作为备用方案）
                    // 但不写入缓存，避免与持有锁的线程冲突
                    return await acquire();
                }
            }
            finally
            {
                // 释放锁
                if (locker != null) await locker.DisposeAsync();
            }
        }

        /// <summary>
        /// 异步获取缓存项，如果不存在则通过指定的委托获取数据并设置缓存
        /// </summary>
        /// <typeparam name="TData">数据类型</typeparam>
        /// <param name="key">缓存键</param>
        /// <param name="acquire">获取数据的委托，可以返回null值</param>
        /// <param name="cacheEntryOptions">缓存项配置选项</param>
        /// <param name="token">取消令牌</param>
        /// <returns>表示异步操作的任务，包含获取的数据。数据可能为null</returns>
        /// <remarks>
        /// 该方法将同步委托包装成异步委托，然后调用异步版本的GetAsync方法
        /// </remarks>
        public Task<TData?> GetAsync<TData>(string key, Func<TData?> acquire, CacheEntryOptions? cacheEntryOptions = null, CancellationToken token = default)
        {
            // 将同步委托包装成异步委托
            Task<TData?> Wrapper() => Task.FromResult(acquire());
            return GetAsync(key, Wrapper, cacheEntryOptions, token);
        }

        /// <summary>
        /// 获取缓存项，如果不存在则通过指定的委托获取数据并设置缓存
        /// </summary>
        /// <typeparam name="TData">数据类型</typeparam>
        /// <param name="key">缓存键</param>
        /// <param name="acquire">获取数据的委托，可以返回null值</param>
        /// <param name="cacheEntryOptions">缓存项配置选项</param>
        /// <returns>获取的数据，可能为null。当返回null时会使用较短的过期时间进行缓存</returns>
        /// <remarks>
        /// 该方法使用分布式锁机制防止缓存击穿：
        /// 1. 首先尝试从缓存中获取数据
        /// 2. 如果缓存不存在，则尝试获取分布式锁
        /// 3. 获取锁成功后，再次检查缓存（双重检查锁定模式）
        /// 4. 如果缓存仍不存在，则执行委托获取数据并更新缓存
        /// 5. 如果未能获取锁，则等待短暂时间后再次尝试从缓存获取
        /// 6. 如果仍未命中缓存，则执行委托获取数据，但不更新缓存，避免与持有锁的线程冲突
        /// </remarks>
        public TData? Get<TData>(string key, Func<TData?> acquire, CacheEntryOptions? cacheEntryOptions = null)
        {
            Check.StringNotNullOrEmpty(key, nameof(key));

            var db = _redisDatabaseContext.Connect();

            // 首次尝试从Redis读取缓存
            var (hit, value) = ReadFromRedis<TData>(db, key);
            if (hit) return value;

            // 获取分布式锁以防止缓存击穿
            var lockKey = GetLockKey(key);

            // 尝试获取分布式锁，最多等待300ms
            var locker = _distributedLock.TryAcquireLock(lockKey,
                TimeSpan.FromMilliseconds(300),
                TimeSpan.FromMilliseconds(_cacheOptions.LockTimeoutMilliseconds));

            try
            {
                if (locker != null)
                {
                    // 获得锁后再次检查缓存（双重检查锁定模式）
                    (hit, value) = ReadFromRedis<TData>(db, key);
                    if (hit) return value;

                    // 缓存确实不存在，执行获取数据的操作
                    var newData = acquire();

                    // 对空值也进行缓存，设置较短的过期时间
                    var options = PrepareEffectiveOptions(newData, cacheEntryOptions);
                    Set(key, newData, options);
                    return newData;
                }
                else
                {
                    // 未获得锁，说明其他线程正在重建缓存
                    // 等待短暂时间后再次尝试从缓存获取
                    Thread.Sleep(100);
                    (hit, value) = ReadFromRedis<TData>(db, key);
                    if (hit) return value;

                    // 如果仍然没有缓存，则执行获取数据的操作（作为备用方案）
                    // 但不写入缓存，避免与持有锁的线程冲突
                    return acquire();
                }
            }
            finally
            {
                // 释放锁
                locker?.Dispose();
            }
        }

        public async Task RemoveAsync(string key, CancellationToken token = default)
        {
            Check.StringNotNullOrEmpty(key, nameof(key));
            token.ThrowIfCancellationRequested();

            var db = await _redisDatabaseContext.ConnectAsync(token).ConfigureAwait(false);
            try
            {
                await db.KeyDeleteAsync(_redisDatabaseContext.InstancePrefix.Append(key)).ConfigureAwait(false);
            }
            catch (Exception ex)
            {
                _redisDatabaseContext.OnRedisError(ex, db);
                throw;
            }
        }

        public async Task RemoveAsync(IEnumerable<string> keys, CancellationToken token = default)
        {
            ArgumentNullException.ThrowIfNull(keys);

            var arr = keys.Where(k => !string.IsNullOrWhiteSpace(k))
                          .Select(k => _redisDatabaseContext.InstancePrefix.Append(k))
                          .ToArray();
            if (arr.Length == 0) return;

            token.ThrowIfCancellationRequested();
            var db = await _redisDatabaseContext.ConnectAsync(token).ConfigureAwait(false);
            try
            {
                await db.KeyDeleteAsync(arr).ConfigureAwait(false);
            }
            catch (Exception ex)
            {
                _redisDatabaseContext.OnRedisError(ex, db);
                throw;
            }
        }

        public void Remove(string key)
        {
            Check.StringNotNullOrEmpty(key, nameof(key));

            var db = _redisDatabaseContext.Connect();
            try
            {
                db.KeyDelete(_redisDatabaseContext.InstancePrefix.Append(key));
            }
            catch (Exception ex)
            {
                _redisDatabaseContext.OnRedisError(ex, db);
                throw;
            }
        }

        public void Remove(IEnumerable<string> keys)
        {
            ArgumentNullException.ThrowIfNull(keys);

            var arr = keys.Where(k => !string.IsNullOrWhiteSpace(k))
                          .Select(k => _redisDatabaseContext.InstancePrefix.Append(k))
                          .ToArray();
            if (arr.Length == 0) return;

            var db = _redisDatabaseContext.Connect();
            try
            {
                db.KeyDelete(arr);
            }
            catch (Exception ex)
            {
                _redisDatabaseContext.OnRedisError(ex, db);
                throw;
            }
        }

        public async Task RemoveByPrefixAsync(string prefix, CancellationToken token = default)
        {
            var db = await _redisDatabaseContext.ConnectAsync(token).ConfigureAwait(false);
            string pattern = $"{_redisDatabaseContext.InstancePrefix}{prefix}*";

            foreach (var server in db.Multiplexer.GetServers())
            {
                if (server.IsReplica) continue;

                token.ThrowIfCancellationRequested();
                try
                {
                    var keys = server.Keys(db.Database, pattern);

                    await BatchDeleteKeysAsync(db, keys, token).ConfigureAwait(false);
                }
                catch (Exception ex)
                {
                    _redisDatabaseContext.OnRedisError(ex, db);
                    throw;
                }
            }
        }

        public void RemoveByPrefix(string prefix)
        {
            var db = _redisDatabaseContext.Connect();
            string pattern = $"{_redisDatabaseContext.InstancePrefix}{prefix}*";

            foreach (var server in db.Multiplexer.GetServers())
            {
                if (server.IsReplica) continue;

                try
                {
                    var keys = server.Keys(db.Database, pattern);

                    BatchDeleteKeys(db, keys);
                }
                catch (Exception ex)
                {
                    _redisDatabaseContext.OnRedisError(ex, db);
                    throw;
                }
            }
        }

        public async Task ClearAsync(CancellationToken token = default)
        {
            var db = await _redisDatabaseContext.ConnectAsync(token).ConfigureAwait(false);
            string pattern = $"{_redisDatabaseContext.InstancePrefix}*";

            foreach (var server in db.Multiplexer.GetServers())
            {
                if (server.IsReplica) continue;

                token.ThrowIfCancellationRequested();
                try
                {
                    // 获取当前实例前缀下的所有键
                    var keys = server.Keys(db.Database, pattern);

                    // 批量删除键
                    await BatchDeleteKeysAsync(db, keys, token).ConfigureAwait(false);
                }
                catch (Exception ex)
                {
                    _redisDatabaseContext.OnRedisError(ex, db);
                    throw;
                }
            }
        }

        public void Clear()
        {
            var db = _redisDatabaseContext.Connect();
            string pattern = $"{_redisDatabaseContext.InstancePrefix}*";

            foreach (var server in db.Multiplexer.GetServers())
            {
                if (server.IsReplica) continue;

                try
                {
                    // 获取当前实例前缀下的所有键
                    var keys = server.Keys(db.Database, pattern);
                    // 批量删除键
                    BatchDeleteKeys(db, keys);
                }
                catch (Exception ex)
                {
                    _redisDatabaseContext.OnRedisError(ex, db);
                    throw;
                }
            }
        }

        public CacheEntryOptions CreateCacheEntryOptions() => _cacheOptions.CreateCacheEntryOptions();

        protected virtual void Dispose(bool disposing)
        { }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        #endregion ICacheManager

        /// <summary>
        /// 从Redis异步读取缓存数据
        /// </summary>
        /// <typeparam name="TData">数据类型</typeparam>
        /// <param name="db">Redis数据库连接</param>
        /// <param name="key">缓存键</param>
        /// <param name="token">取消令牌</param>
        /// <returns>缓存的数据(如果存在)，否则返回null</returns>
        private async Task<(bool, TData?)> ReadFromRedisAsync<TData>(IDatabase db, string key, CancellationToken token)
        {
            RedisValue[] results;
            try
            {
                results = await db.HashGetAsync(_redisDatabaseContext.InstancePrefix.Append(key), GetHashFields(true)).ConfigureAwait(false);
            }
            catch (Exception ex)
            {
                _redisDatabaseContext.OnRedisError(ex, db);
                throw;
            }

            // 刷新过期时间
            if (results.Length >= 2)
            {
                MapMetadata(results, out DateTime? absExp, out TimeSpan? sldExp);
                await RefreshAsync(db, key, absExp, sldExp, token);
            }

            // 获取缓存的数据
            if (results.Length >= 3 && !results[2].IsNull)
            {
                var data = DeserializeObject<TData>(results[2]);
                // 即使反序列化后为null也返回,因为可能是存储的空值缓存
                return (true, data);
            }

            return (false, default);
        }

        /// <summary>
        /// 从Redis同步读取缓存数据
        /// </summary>
        /// <typeparam name="TData">数据类型</typeparam>
        /// <param name="db">Redis数据库连接</param>
        /// <param name="key">缓存键</param>
        /// <returns>缓存的数据(如果存在)，否则返回null</returns>
        private (bool, TData?) ReadFromRedis<TData>(IDatabase db, string key)
        {
            RedisValue[] results;
            try
            {
                results = db.HashGet(_redisDatabaseContext.InstancePrefix.Append(key), GetHashFields(true));
            }
            catch (Exception ex)
            {
                _redisDatabaseContext.OnRedisError(ex, db);
                throw;
            }

            // 刷新过期时间
            if (results.Length >= 2)
            {
                MapMetadata(results, out DateTime? absExp, out TimeSpan? sldExp);
                Refresh(db, key, absExp, sldExp);
            }

            // 获取缓存的数据
            if (results.Length >= 3 && !results[2].IsNull)
            {
                var data = DeserializeObject<TData>(results[2]);
                // 即使反序列化后为null也返回,因为可能是存储的空值缓存
                return (true, data);
            }

            return (false, default);
        }

        private void Refresh(IDatabase db, string key, DateTime? absExp, TimeSpan? sldExp)
        {
            if (!sldExp.HasValue) return;

            TimeSpan expr;
            if (absExp.HasValue)
            {
                var rel = absExp.Value - DateTime.UtcNow;
                if (rel <= TimeSpan.Zero) return;
                expr = rel <= sldExp.Value ? rel : sldExp.Value;
            }
            else
            {
                expr = sldExp.Value;
            }

            if (expr <= TimeSpan.Zero) return;

            try
            {
                db.KeyExpire(_redisDatabaseContext.InstancePrefix.Append(key), expr);
            }
            catch (Exception ex)
            {
                _redisDatabaseContext.OnRedisError(ex, db);
                throw;
            }
        }

        private async Task RefreshAsync(IDatabase db, string key, DateTime? absExp, TimeSpan? sldExp, CancellationToken token)
        {
            token.ThrowIfCancellationRequested();
            if (!sldExp.HasValue) return;

            // 绝对过期时间和滑动过期时间共同决定最终的过期时间
            TimeSpan expr;
            if (absExp.HasValue)
            {
                var rel = absExp.Value - DateTime.UtcNow;
                if (rel <= TimeSpan.Zero) return;
                expr = rel <= sldExp.Value ? rel : sldExp.Value;
            }
            else
            {
                expr = sldExp.Value;
            }

            if (expr <= TimeSpan.Zero) return;

            try
            {
                await db.KeyExpireAsync(_redisDatabaseContext.InstancePrefix.Append(key), expr).ConfigureAwait(false);
            }
            catch (Exception ex)
            {
                _redisDatabaseContext.OnRedisError(ex, db);
                throw;
            }
        }

        private static void MapMetadata(RedisValue[] results, out DateTime? absoluteExpiration, out TimeSpan? slidingExpiration)
        {
            absoluteExpiration = null;
            slidingExpiration = null;

            // 解析绝对过期时间
            if (results.Length > 0 && !results[0].IsNull &&
                long.TryParse(results[0].ToString(), out var absTicks) &&
                absTicks != NotPresent)
            {
                absoluteExpiration = new DateTime(absTicks, DateTimeKind.Utc);
            }

            // 解析滑动过期时间
            if (results.Length > 1 && !results[1].IsNull &&
                long.TryParse(results[1].ToString(), out var sldTicks) &&
                sldTicks != NotPresent)
            {
                slidingExpiration = new TimeSpan(sldTicks);
            }
        }

        /// <summary>
        /// 获取绝对过期的时间
        /// </summary>
        /// <param name="creationTime"></param>
        /// <param name="options"></param>
        /// <returns></returns>
        /// <exception cref="ArgumentOutOfRangeException"></exception>
        private static DateTime? GetAbsoluteExpiration(DateTime creationTime, CacheEntryOptions options)
        {
            ArgumentNullException.ThrowIfNull(options);

            // 1. 相对绝对过期优先
            if (options.AbsoluteExpirationRelativeToNow.HasValue)
                return creationTime + options.AbsoluteExpirationRelativeToNow.Value;

            // 2. 固定绝对过期
            if (options.AbsoluteExpiration.HasValue)
            {
                var abs = options.AbsoluteExpiration.Value;

                // 约定：调用方负责传入 UTC。如需强制，可加：
                // if (abs.Kind != DateTimeKind.Utc) throw new ArgumentException("AbsoluteExpiration 必须为 UTC 时间。");

                if (abs <= creationTime)
                    throw new ArgumentOutOfRangeException(nameof(options), abs, "AbsoluteExpiration 必须晚于当前创建时间。");

                return abs;
            }

            // 3. 未设置
            return null;
        }

        /// <summary>
        /// 获取过期时间设置（秒）
        /// </summary>
        private static long? GetExpirationInSeconds(DateTime creationTime, DateTime? absoluteExpiration, CacheEntryOptions options)
        {
            if (absoluteExpiration.HasValue)
            {
                var remain = absoluteExpiration.Value - creationTime;
                if (remain <= TimeSpan.Zero) return 0;
                if (options.SlidingExpiration.HasValue)
                {
                    var sld = options.SlidingExpiration.Value;
                    var seconds = (long)Math.Min(remain.TotalSeconds, sld.TotalSeconds);
                    return seconds < 1 ? 1 : seconds;
                }
                var sAbs = (long)remain.TotalSeconds;
                return sAbs < 1 ? 1 : sAbs;
            }

            if (options.SlidingExpiration.HasValue)
            {
                var sSld = (long)options.SlidingExpiration.Value.TotalSeconds;
                return sSld < 1 ? 1 : sSld;
            }

            return null;
        }

        /// <summary>
        /// 构造有效选项（空值使用 NullValue 策略）
        /// </summary>
        private CacheEntryOptions PrepareEffectiveOptions(object? data, CacheEntryOptions? provided)
        {
            if (data == null)
                return _cacheOptions.CreateNullValueCacheEntryOptions();

            return provided ?? _cacheOptions.CreateCacheEntryOptions();
        }

        private static async Task BatchDeleteKeysAsync(IDatabase db, IEnumerable<RedisKey> keys, CancellationToken token = default)
        {
            var buffer = new List<RedisKey>(DeleteKeysBatchSize);
            foreach (var key in keys)
            {
                token.ThrowIfCancellationRequested();
                buffer.Add(key);
                if (buffer.Count >= DeleteKeysBatchSize)
                {
                    await db.KeyDeleteAsync(buffer.ToArray()).ConfigureAwait(false);
                    buffer.Clear();
                }
            }
            if (buffer.Count > 0)
                await db.KeyDeleteAsync(buffer.ToArray()).ConfigureAwait(false);
        }

        private static void BatchDeleteKeys(IDatabase db, IEnumerable<RedisKey> keys)
        {
            var buffer = new List<RedisKey>(DeleteKeysBatchSize);
            foreach (var key in keys)
            {
                buffer.Add(key);
                if (buffer.Count >= DeleteKeysBatchSize)
                {
                    db.KeyDelete(buffer.ToArray());
                    buffer.Clear();
                }
            }

            if (buffer.Count > 0)
                db.KeyDelete(buffer.ToArray());
        }
    }
}